
/* GCSx
** TILESETPAINT.CPP
**
** Tileset editing- actual paint/edit window
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Tile edit window- toolbar

const ToolSelect::ToolIconStruct TilePaintTools::toolIcons[TOOLSELECT_NUM_TOOLS] = {
    { TOOLS_PEN, 0, 0, "Draw (pixel by pixel)" },
    { TOOLS_LINE, 20, 0, "Straight line" },
    { TOOLS_DROPPER, 40, 40, "Dropper (grab/select color)" },
    { TOOLS_FILL, 80, 0, "Flood fill" },
    { TOOLS_RECT, 80, 60, "Rectangle (outline)" },
    { TOOLS_RECTFILL, 40, 0, "Rectangle (filled)" },
    { TOOLS_ELLIPSE, 40, 80, "Ellipse (outline)" },
    { TOOLS_ELLIPSEFILL, 60, 0, "Ellipse (filled)" },
    { TOOLS_SELECT, 0, 20, "Select area (rectangle)" },
    { TOOLS_SELECTELLIPSE, 20, 20, "Select area (ellipse)" },
    { TOOLS_WAND, 40, 20, "Magic wand (select area by color)" },
};

const ToolSelect::ToolIconStruct TilePaintTools::dotsIcons[TOOLSELECT_NUM_DOTS] = {
    { VIEW_DOTS_NONE, 20, 100, "Don't show dots" },
    { VIEW_DOTS_TRANS, 40, 100, "Show dots on transparent pixels" },
    { VIEW_DOTS_ALPHA, 60, 100, "Show alpha as dots" },
};

Window::WindowSort TilePaintTools::windowSort() const { start_func
    return WINDOWSORT_ONTOP;
}

void TilePaintTools::adjustForTool() { start_func
    WCheckBox* check = dynamic_cast<WCheckBox*>(findWidget(ID_ANTIALIAS));
    
    if ((*toolPtr == TOOLS_WAND) || (*toolPtr == TOOLS_FILL)) {
        check->enable();
        check->changeText("Contiguous");
        check->setToolTip("Contiguous will only fill or select a single, connected area");
        
        findWidget(ID_TOLERANCELABEL)->enable();
        findWidget(ID_TOLERANCE_S)->enable();
        findWidget(ID_TOLERANCE_N)->enable();
    }
    else {
        check->changeText("Smooth");
        check->setToolTip("Draw smooth lines and circles (antialiased)");
        if ((*toolPtr == TOOLS_LINE) || (*toolPtr == TOOLS_ELLIPSE) || (*toolPtr == TOOLS_ELLIPSEFILL)) check->enable();
        else check->disable();

        findWidget(ID_TOLERANCELABEL)->disable();
        findWidget(ID_TOLERANCE_S)->disable();
        findWidget(ID_TOLERANCE_N)->disable();
    }
}

int TilePaintTools::event(int hasFocus, const SDL_Event* event) { start_func
    if (event->type == SDL_COMMAND) {
        // Simple cases
        switch (event->user.code) {
            case VIEW_GRID:
                *gridPtr = *gridPtr ? 0 : 1;
                findWidget(ID_GRID)->load();
                paintArea->refresh();
                config->write(TILEPAINT_GRID, *gridPtr);
                return 1;
                
            case TOOLS_BLENDUP:
            case TOOLS_BLENDDOWN:
                if (event->user.code == TOOLS_BLENDUP) {
                    if (((*alphaBlendPtr) += 10) > 100) *alphaBlendPtr = 100;
                }
                else {
                    if (((*alphaBlendPtr) -= 10) < 0) *alphaBlendPtr = 0;
                }
                findWidget(ID_ALPHA_S)->load();
                findWidget(ID_ALPHA_N)->load();
                paintArea->refresh();
                return 1;

            case TOOLS_TOLERANCEUP:
            case TOOLS_TOLERANCEDOWN:
                if (event->user.code == TOOLS_TOLERANCEUP) {
                    if (((*tolerancePtr) += 10) > 100) *tolerancePtr = 100;
                }
                else {
                    if (((*tolerancePtr) -= 10) < 0) *tolerancePtr = 0;
                }
                findWidget(ID_TOLERANCE_S)->load();
                findWidget(ID_TOLERANCE_N)->load();
                config->write(TILEPAINT_TOLERANCE, *tolerancePtr);
                return 1;

            case TOOLS_ANTIALIAS:
            case TOOLS_CONTIGUOUS:
                *antiAliasPtr = *antiAliasPtr ? 0 : 1;
                findWidget(ID_ANTIALIAS)->load();
                config->write(TILEPAINT_ANTIALIAS, *antiAliasPtr);
                return 1;
        }
    
        // Choose tool or dots
        int newTool = 0;
        int newDots = 0;
        WButton* toolButton = dynamic_cast<WButton*>(findWidget(ID_TOOL));
        WButton* dotsButton = dynamic_cast<WButton*>(findWidget(ID_DOTS));
        int x, y;
        
        if (event->user.code == TOOLS_CHOOSE) {
            newTool = toolSelect->run(toolButton->getScreenX(), toolButton->getScreenY() + toolButton->getHeight());
        }
        else if (toolSelect->isTool(event->user.code)) {
            newTool = event->user.code;
            paintArea->startToolIfMove();
        }
        else if (event->user.code == VIEW_DOTS_CHOOSE) {
            newDots = dotsSelect->run(dotsButton->getScreenX(), dotsButton->getScreenY() + dotsButton->getHeight());
        }
        else if (dotsSelect->isTool(event->user.code)) {
            newDots = event->user.code;
        }
        
        if (newTool) {
            if (toolSelect->getToolIcon(newTool, x, y)) {
                toolButton->changeIcon(x, y);
                *toolPtr = newTool;
                adjustForTool();
                return 1;
            }
        }
        
        if (newDots) {
            if (dotsSelect->getToolIcon(newDots, x, y)) {
                dotsButton->changeIcon(x, y);
                *dotsPtr = newDots;
                paintArea->refresh();
                config->write(TILEPAINT_DOTS, *dotsPtr);
                return 1;
            }
        }
        
        if (event->user.code == TOOLS_CHOOSE) return 1;
        if (event->user.code == VIEW_DOTS_CHOOSE) return 1;
    }

    return Dialog::event(hasFocus, event);
}

void TilePaintTools::childModified(Window* modified) { start_func
    Dialog::childModified(modified);
    
    Widget* widget = dynamic_cast<Widget*>(modified);
    
    if (widget) {
        // If certain settings modified, tile area needs to refresh
        if ((widget->getId() == ID_GRID) || (widget->getId() == ID_ALPHA_S) || (widget->getId() == ID_ALPHA_N)) {
            paintArea->refresh();
        }

        // Save config settings
        config->write(TILEPAINT_ANTIALIAS, *antiAliasPtr);
        config->write(TILEPAINT_TOLERANCE, *tolerancePtr);
        config->write(TILEPAINT_GRID, *gridPtr);
    }
}

void TilePaintTools::tileRedraw() { start_func
    assert(findWidget(ID_PREVIEW));
    // (doesn't throw exceptions)
    findWidget(ID_PREVIEW)->load();
}

int TilePaintTools::wantsToBeDeleted() const { start_func
    return 1;
}

FrameWindow* TilePaintTools::createWindowed() { start_func
    // Prevent duplication
    if (myFrame) {
        return myFrame;
    }

    // We remember the frame pointer even though it'll delete itself
    myFrame = new FrameWindow("Tools", FrameWindow::RESIZING_SNAP, FrameWindow::FRAMETYPE_DIALOG, this, FrameWindow::TITLEBAR_TOOL, 0);
    return myFrame;
}

TilePaintTools::TilePaintTools(TilePaint* tPaintArea, SDL_Surface** tileSource, int* tool, int* alphaBlend, int* antiAlias, int* tolerance, int* grid, int* dots) : Dialog(blankString, 1, 0, 1) { start_func
    myFrame = NULL;
    paintArea = tPaintArea;
    toolPtr = tool;
    antiAliasPtr = antiAlias;
    alphaBlendPtr = alphaBlend;
    tolerancePtr = tolerance;
    gridPtr = grid;
    dotsPtr = dots;

    Widget* w = NULL;
    WNumberBox* n = NULL;
    
    toolSelect = new ToolSelect(getIconSurface(), TOOLSELECT_NUM_TOOLS, toolIcons);
    dotsSelect = new ToolSelect(getIconSurface(), TOOLSELECT_NUM_DOTS, dotsIcons);
    
    int x = 0;
    int y = 0;
    toolSelect->getToolIcon(*toolPtr, x, y);

    w = new WButton(ID_TOOL, getIconSurface(), x, y, 20, 20, Dialog::BUTTON_NOTHING, TOOLS_CHOOSE);
    w->setToolTip("Change tool");
    w->addTo(this);
    
    w = new WCheckBox(ID_ANTIALIAS, "Smooth", antiAlias, 1);
    w->setToolTip("Draw smooth lines and circles (antialiased)");
    w->addTo(this);
    
    arrangeRow();

    w = new WStatic(ID_ALPHALABEL, "Blend %:");
    w->addTo(this);
    
    w = n = new WNumberBox(ID_ALPHA_N, alphaBlend, 0, 100);
    w->setToolTip("Blend controls translucency of tools and pasting");
    w->addTo(this);
    
    w = new WSlider(ID_ALPHA_S, 0, 100, alphaBlend);
    w->setToolTip("Blend controls translucency of tools and pasting");
    dynamic_cast<WSlider*>(w)->linkTo(n);
    w->addTo(this);
    

    w = new WStatic(ID_TOLERANCELABEL, "Tolerance:");
    w->addTo(this);
    
    w = n = new WNumberBox(ID_TOLERANCE_N, tolerance, 0, 100);
    w->setToolTip("Higher tolerance will affect a wider range of color");
    w->addTo(this);
    
    w = new WSlider(ID_TOLERANCE_S, 0, 100, tolerance);
    w->setToolTip("Higher tolerance will affect a wider range of color");
    dynamic_cast<WSlider*>(w)->linkTo(n);
    w->addTo(this);
    
    makePretty(3);

    w = new WButton(ID_COPY, getIconSurface(), 60, 20, 20, 20, Dialog::BUTTON_NOTHING, EDIT_COPY);
    w->setToolTip("Copy");
    w->addTo(this);
    
    w = new WButton(ID_PASTE, getIconSurface(), 80, 20, 20, 20, Dialog::BUTTON_NOTHING, EDIT_PASTE);
    w->setToolTip("Paste (as new selection)");
    w->addTo(this);
    
    w = new WCheckBox(ID_GRID, getIconSurface(), 0, 100, 20, 20, grid, 1);
    w->setToolTip("Show or hide grid");
    w->addTo(this);
    
    arrangeRow();

    w = new WButton(ID_ZOOMIN, getIconSurface(), 0, 80, 20, 20, Dialog::BUTTON_NOTHING, VIEW_ZOOMIN);
    w->setToolTip("Zoom in");
    w->addTo(this);
    
    w = new WButton(ID_ZOOMOUT, getIconSurface(), 20, 80, 20, 20, Dialog::BUTTON_NOTHING, VIEW_ZOOMOUT);
    w->setToolTip("Zoom out");
    w->addTo(this);
    
    w = new WButton(ID_PREV, getIconSurface(), 60, 80, 20, 20, Dialog::BUTTON_NOTHING, VIEW_PREV);
    w->setToolTip("Previous image");
    w->addTo(this);
    
    w = new WButton(ID_NEXT, getIconSurface(), 80, 80, 20, 20, Dialog::BUTTON_NOTHING, VIEW_NEXT);
    w->setToolTip("Next image");
    w->addTo(this);

    dotsSelect->getToolIcon(*dotsPtr, x, y);
    w = new WButton(ID_DOTS, getIconSurface(), x, y, 20, 20, Dialog::BUTTON_NOTHING, VIEW_DOTS_CHOOSE);
    w->setToolTip("Control display of alpha dots");
    w->addTo(this);
    
    arrangeRow();
    
    // Request initial height based on tile, although this will inevitably be resized
    int previewHeight = 96;
    if (*tileSource) previewHeight = (*tileSource)->h * 3;
    w = new WPreview(ID_PREVIEW, tileSource, 166, previewHeight, 1, 0);
    w->setToolTip("Preview");
    w->addTo(this);
    
    arrangeRow();
    
    // Our height if preview is 0 pixels high
    minHeight = height - previewHeight;
    
    adjustForTool();
    runAsPanel();
}

TilePaintTools::~TilePaintTools() { start_func
    delete toolSelect;
    delete dotsSelect;
}

void TilePaintTools::resize(int newWidth, int newHeight, int newViewWidth, int newViewHeight, int fromParent) { start_func
    if (newViewHeight == -1) newViewHeight = viewHeight;
    
    if (newViewHeight >= 0) {
        // Resize tile preview to fit
        WPreview* widget = dynamic_cast<WPreview*>(findWidget(ID_PREVIEW));
        if (widget) {
            int previewHeight = newViewHeight - minHeight;
            if (previewHeight > 0) {
                widget->changeSize(166, previewHeight);
                // Doesn't throw exceptions
                widget->load();
                newHeight = newViewHeight;
            }
            else {
                widget->changeSize(166, 0);
                // Doesn't throw exceptions
                widget->load();
                newHeight = minHeight;
            }
        }
    }

    Window::resize(newWidth, newHeight, newViewWidth, newViewHeight, fromParent);
}

// Tile edit window- Main

TilePaint::TilePaint(TileSetEdit* myTileset, int myTile, int myIsColl) throw_File : Window() { start_func
    assert(myTileset);
    assert(myTile > 0);
    assert(myTile <= (myIsColl ? myTileset->getCollisionCount() : myTileset->getCount()));
    
    cursorX = cursorY = virtualCursorX = virtualCursorY = 0;
    hideCursor = 0;
    haveFocus = partialFocus = 0;
    toolActive = startToolOnMove = 0;
    dirtyRange.w = 0;
    selectionRect.w = 0;
    selectionMode = SELECTION_OUTLINE;
    selectionXOffs = 0;
    selectionYOffs = 0;
    
    tileset = myTileset;
    world = tileset->getWorldEdit();
    isColl = myIsColl;
    
    // Intentionally doesn't remember last tool selected
    tool = TOOLS_PEN;
    // Intentionally doesn't remember last alpha blend
    alphaBlend = 100;

    // Recall last settings
    antiAlias = config->readNum(TILEPAINT_ANTIALIAS) ? 1 : 0;
    tolerance = config->readNum(TILEPAINT_TOLERANCE);
    if (tolerance < 0) tolerance = 0;
    if (tolerance > 100) tolerance = 100;
    if (isColl) {
        antiAlias = 0;
        tolerance = 100;
    }
    enableGrid = config->readNum(TILEPAINT_GRID) ? 1 : 0;
    drawDots = config->readNum(TILEPAINT_DOTS);
    if ((drawDots != VIEW_DOTS_TRANS) && (drawDots != VIEW_DOTS_ALPHA)) drawDots = VIEW_DOTS_NONE;
    pixelSize = config->readNum(TILEPAINT_ZOOM);
    if (pixelSize >= MAX_ZOOMGRID) gridSize = enableGrid ? 1 : 0;
    else gridSize = 0;

    myFrame = NULL;
    colorbar = NULL;
    tools = NULL;
    tile = NULL;
    alphaWorkspace = NULL;
    selection = NULL;
    backupSelection = NULL;
    tileset->markLock(); // Exception point
    
    FrameWindow* toolsFrame = NULL;
    FrameWindow* colorbarFrame = NULL;
    
    currentTile = myTile; // So changetile isn't called
    reloadTileStats();
    changeTile(myTile);

    colorbar = new ColorSelect(&colors, tileset->getDefaultTransparent());
    tools = new TilePaintTools(this, &tile, &tool, &alphaBlend, &antiAlias, &tolerance, &enableGrid, &drawDots);

    colorbarFrame = colorbar->createWindowed();
    toolsFrame = tools->createWindowed();
    
    myFrame = new FrameWindow(blankString, FrameWindow::RESIZING_NORMAL, FrameWindow::FRAMETYPE_BEVEL_BK, this);
    myFrame->setAutoCenter(1);
    updateTitlebar();
    myFrame->addToolPanel(colorbarFrame, FrameWindow::CLIENT_RIGHT);
    myFrame->addToolPanel(toolsFrame, FrameWindow::CLIENT_RIGHT);
    myFrame->show(FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CURRMIN, FrameWindow::SHOW_CURRMIN);
    
    // Disabled/etc status on various tools
    if (pixelSize == 1) tools->findWidget(TilePaintTools::ID_ZOOMOUT)->disable();
    if (pixelSize >= MAX_ZOOM) tools->findWidget(TilePaintTools::ID_ZOOMIN)->disable();
    tools->findWidget(TilePaintTools::ID_COPY)->disable();
}

TilePaint::~TilePaint() { start_func
    if (tile) SDL_FreeSurface(tile);
    if (alphaWorkspace) SDL_FreeSurface(alphaWorkspace);
    if (selection) SDL_FreeSurface(selection);
    if (backupSelection) SDL_FreeSurface(backupSelection);
    if (tileset) tileset->markUnlock();
}

void TilePaint::setDirtyRect(const Rect& rect, int selectionFeather) { start_func
    // Clip
    Rect bound = { 0, 0, tileWidth, tileHeight };
    if (intersectRects(bound, rect)) {
        // Scale to pixel size
        bound.x *= pixelSize;
        bound.y *= pixelSize;
        bound.w = bound.w * pixelSize + 1;
        bound.h = bound.h * pixelSize + 1;
        
        // Feather for selection modification?
        if (selectionFeather) {
            if (bound.x > 0) {
                --bound.x;
                ++bound.w;
            }
            if (bound.y > 0) {
                --bound.y;
                ++bound.h;
            }
            // We don't bother checking if our w/h goes over the limit-
            // this will get clipped; but negative x/y is too odd to allow, above
            ++bound.w;
            ++bound.h;
        }

        // Add rectangle into dirty range
        boundRects(dirtyRange, bound);
        setDirty();
    }
}

void TilePaint::setDirtyGlyphWidth(int x1, int x2) { start_func
    // Create a rectangle
    if (x1 > x2) swap(x1, x2);
    Rect glyph = { x1 * pixelSize - GLYPH_SIZE_POINTER_HEIGHT, 0, (x2 - x1) * pixelSize + 1 + GLYPH_SIZE_POINTER_HEIGHT * 2, height };
    boundRects(dirtyRange, glyph);
    setDirty();
}

void TilePaint::setDirtyBox(int x1, int y1, int x2, int y2, int selectionFeather) { start_func
    // Create a rectangle
    // Add rectangle into dirty range
    setDirtyRect(createRect(x1, y1, x2, y2), selectionFeather);
}

void TilePaint::changeTile(int tileNum) { start_func
    assert(tile);
    assert(tileset);
    assert(tileNum > 0);
    assert(tileNum <= (isColl ? tileset->getCollisionCount() : tileset->getCount()));

    currentTile = tileNum;
    refreshTile();
    if (tools) tools->tileRedraw();
    setDirty(1);
    updateTitlebar();
}

void TilePaint::refreshTile() { start_func
    assert(tile);
    assert(tileset);
    assert(currentTile > 0);
    assert(currentTile <= (isColl ? tileset->getCollisionCount() : tileset->getCount()));

    if (isColl) tileset->loadColl(currentTile, tile);
    else tileset->loadTile(currentTile, tile);
    if (isFont) glyphWidth = tileset->getGlyphWidth(currentTile);
}

void TilePaint::applyTile() { start_func
    assert(tile);
    assert(tileset);
    assert(currentTile > 0);
    assert(currentTile <= (isColl ? tileset->getCollisionCount() : tileset->getCount()));

    if (isColl) tileset->saveTile(currentTile, tile, this);
    else tileset->saveTile(currentTile, tile, this);
    tools->tileRedraw();
}

void TilePaint::applyGlyphWidth() { start_func
    assert(currentTile > 0);
    // (can't be a collision map)
    assert(currentTile <= tileset->getCount());

    tileset->setGlyphWidth(currentTile, glyphWidth, this);
}

void TilePaint::setGlyphWidth(int gX) { start_func
    if (glyphWidth != gX) {
        if (gX < 0) gX = 0;
        if (gX > tileWidth) gX = tileWidth;
        setDirtyGlyphWidth(gX, glyphWidth);
        glyphWidth = gX;
    }
}

void TilePaint::changePixelSizing(int newPixelSize, int newGridSize) { start_func
    assert(newPixelSize > 0);
    assert(newGridSize >= 0);
    assert(newPixelSize > newGridSize);

    pixelSize = newPixelSize;
    gridSize = newGridSize;

    config->write(TILEPAINT_ZOOM, pixelSize);
    
    resize(tileWidth * pixelSize + 1, tileHeight * pixelSize + 1 + (isFont ? GLYPH_SIZE_POINTER_HEIGHT + GLYPH_SIZE_POINTER_MARGIN : 0));
    if (myFrame) myFrame->requestViewSize(width, height);
}

void TilePaint::reloadTileStats() { start_func
    int oldWidth = tileWidth;
    int oldHeight = tileHeight;

    if (isColl) {
        numTiles = tileset->getCollisionCount();
        // Even if fonts can have maps, we don't want glyphs on maps!
        isFont = 0;
    }
    else {
        numTiles = tileset->getCount();
        isFont = tileset->getIsFont();
    }
    tileWidth = tileset->getWidth();
    tileHeight = tileset->getHeight();

    // Close if no tiles left    
    if (numTiles == 0) {
        closeWindow();
        return;
    }
    // If numtiles > 0, make sure current tile still exists
    else if (numTiles < currentTile) {
        changeTile(numTiles);
    }
    else {
        // Update titlebar (changetile would've done so, above)
        updateTitlebar();
    }
    
    // Ensure within bounds
    moveCursor(cursorX, cursorY);
    
    // Reallocate surfaces
    SDL_Surface* newTile = NULL;
    
    newTile = createSurface32(tileWidth, tileHeight);
    if (tile) {
        SDL_FreeSurface(tile);
    }
    tile = newTile;
    newTile = NULL;

    newTile = createSurface32(tileWidth, tileHeight);
    if (alphaWorkspace) {
        SDL_FreeSurface(alphaWorkspace);
    }
    alphaWorkspace = newTile;
    newTile = NULL;
    SDL_SetAlpha(alphaWorkspace, SDL_SRCALPHA, 255);

    // Selection must be filled/copied over
    newTile = createSurface32(tileWidth, tileHeight);
    sge_FilledRect(newTile, 0, 0, tileWidth, tileHeight, mapColor32(0, 0, 0, 0));
    if (selection) {
        blit(0, 0, selection, 0, 0, newTile, min(oldWidth, tileWidth), min(oldHeight, tileHeight));
        SDL_FreeSurface(selection);
        // Clip selection rect also
        Rect newSize = { 0, 0, tileWidth, tileHeight };
        intersectRects(selectionRect, newSize);
    }
    selection = newTile;
    newTile = NULL;

    newTile = createSurface32(tileWidth, tileHeight);
    sge_FilledRect(newTile, 0, 0, tileWidth, tileHeight, mapColor32(0, 0, 0, 0));
    if (backupSelection) {
        blit(0, 0, backupSelection, 0, 0, newTile, min(oldWidth, tileWidth), min(oldHeight, tileHeight));
        SDL_FreeSurface(backupSelection);
    }
    backupSelection = newTile;
    newTile = NULL;
    
    resize(tileWidth * pixelSize + 1, tileHeight * pixelSize + 1 + (isFont ? GLYPH_SIZE_POINTER_HEIGHT + GLYPH_SIZE_POINTER_MARGIN : 0));
    if (myFrame) {
        myFrame->setScroll(pixelSize, pixelSize);
        myFrame->requestViewSize(width, height);
    }
}

void TilePaint::updateTitlebar() { start_func
    if (myFrame) {
        if (isColl) myFrame->setTitle(formatString("%s : %s : Collision Map %d / %d", world->getTitle().c_str(), tileset->getName().c_str(), currentTile, numTiles));
        else if (isFont) myFrame->setTitle(formatString("%s : %s : Glyph %d / %d (%c)", world->getTitle().c_str(), tileset->getName().c_str(), currentTile, numTiles, currentTile + 31));
        else myFrame->setTitle(formatString("%s : %s : Image %d / %d", world->getTitle().c_str(), tileset->getName().c_str(), currentTile, numTiles));
    }
}

int TilePaint::event(int hasFocus, const SDL_Event* event) { start_func
    int changed = 0;
    int clickX, clickY;
    ObjChange* obj;

    switch (event->type) {
        case SDL_CLOSE:
            // Merge any existing selection
            try {
                mergeSelection();
            }
            catch (UndoException& e) {
            }
            return 1;
    
        case SDL_SPECIAL:
            // Refresh selection rectangle?
            if ((event->user.code == SDL_IDLEPHASE) && (partialFocus)) {
                Rect rect = { selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h };
                setDirtyRect(rect);
            }
            // Refresh cursor?
            if (((event->user.code == SDL_IDLECURSOR) || (event->user.code == SDL_IDLEPHASE)) && (haveFocus)) {
                Rect rect = { cursorX, cursorY, 1, 1 };
                setDirtyRect(rect);
            }
            return 1;

        case SDL_COMMAND:
            switch (event->user.code) {    
                case VIEW_NEXT:
                    // Merge any existing selection
                    try {
                        mergeSelection();
                    }
                    catch (UndoException& e) {
                        return 1;
                    }
                    cancelTool();
                    if (currentTile < numTiles) changeTile(currentTile + 1);
                    else changeTile(1);
                    return 1;

                case VIEW_PREV:
                    // Merge any existing selection
                    try {
                        mergeSelection();
                    }
                    catch (UndoException& e) {
                        return 1;
                    }
                    cancelTool();
                    if (currentTile > 1) changeTile(currentTile - 1);
                    else changeTile(numTiles);
                    return 1;
                    
                case VIEW_ZOOMOUT:
                    zoomOut();
                    return 1;
                    
                case VIEW_ZOOMIN:
                    zoomIn();
                    return 1;
                    
                case EDIT_UNDO:
                    // finish instead of cancel- this causes undo to undo current
                    // tool if one is being used
                    finishTool();
                    // (let world handle from here)
                    break;
                    
                case EDIT_REDO:
                    cancelTool();
                    // (let world handle from here)
                    break;
                    
                case EDIT_COPY:
                    copySelection();
                    return 1;
                    
                case EDIT_CUT:
                    try {
                        world->undo.preUndoBlock();
                        copySelection();
                        deleteSelection();
                        clearSelection();
                        world->undo.postUndoBlock();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;
                    
                case EDIT_DELETE:
                    try {
                        deleteSelection();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;
                    
                case EDIT_SELECTALL:
                    try {
                        world->undo.preUndoBlock();
                        // Merge any floating
                        mergeSelection();
                        // Store undo for entire selection
                        undoStoreSelect(0, 0, tileWidth, tileHeight);
                        world->undo.postUndoBlock();

                        // Select all
                        sge_FilledRect(selection, 0, 0, tileWidth, tileHeight, mapColor32(0, 0, 0, 255));
                        selectionRect.x = 0;
                        selectionRect.y = 0;
                        selectionRect.w = tileWidth;
                        selectionRect.h = tileHeight;
                        setDirty(1);
                        tools->findWidget(TilePaintTools::ID_COPY)->enable();
                    }
                    catch (UndoException& e) {
                    }

                    return 1;
                    
                case EDIT_DESELECTALL:
                    cancelTool();
                    try {
                        doneSelection();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;
                    
                case EDIT_PASTE:
                    try {
                        pasteSelection();
                    }
                    catch (UndoException& e) {
                        return 1;
                    }
                    
                    // Make sure a selection tool is current
                    if ((!toolActive) && (tool != TOOLS_SELECT) && (tool != TOOLS_SELECTELLIPSE) && (tool != TOOLS_WAND)) {
                        desktop->broadcastEvent(SDL_COMMAND, TOOLS_SELECT);
                        desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
                    }
                    
                    return 1;
                    
                case EDIT_SETGLYPHWIDTH:
                    if (isFont) {
                        try {
                            undoStoreGlyph();
                        }
                        catch (UndoException& e) {
                            return 1;
                        }
                        setGlyphWidth(cursorX + 1);
                        applyGlyphWidth();
                        return 1;
                    }
                    break;
                    
                case CMD_RELEASE:
                    finishTool(); // Clears startToolOnMove too; safe even if no tool
                    moveCursor(cursorX, cursorY);
                    return 1;

                case TOOLS_NEXTCOLOR:
                    colorbar->colorSelection(ColorSelect::SELECTED_FG,
                                             colorbar->colorSelection(ColorSelect::SELECTED_FG) + 1);
                    return 1;

                case TOOLS_PREVCOLOR:
                    colorbar->colorSelection(ColorSelect::SELECTED_FG,
                                             colorbar->colorSelection(ColorSelect::SELECTED_FG) - 1);
                    return 1;

                case TOOLS_EDITCOLOR:
                    colorbar->editColor(colorbar->colorSelection(ColorSelect::SELECTED_FG));
                    return 1;
            }
            
            if (world->commandEvent(event->user.code)) return 1;
            break;
    
        case SDL_OBJECTCHANGE:
            obj = (ObjChange*)event->user.data1;
            if ((event->user.code & OBJ_TILESET) && (obj->obj == tileset)) {
                if (event->user.code & OBJMOD_DEFTRANS) {
                    colorbar->changeDefaultTransparent(tileset->getDefaultTransparent());
                }
                if (event->user.code & OBJMOD_DELETE) {
                    tileset = NULL;
                    closeWindow();
                }
                if (event->user.code & OBJMOD_NAME) {
                    updateTitlebar();
                }
                if (event->user.code & (OBJMOD_WIDTH | OBJMOD_HEIGHT | OBJMOD_COUNT | OBJMOD_COUNTCOLL)) {
                    reloadTileStats();
                    if (numTiles) {
                        cancelTool(); // After reloading stats, so refresh works
                        // Reload tile, resizing may have been from an undo
                        refreshTile();
                    }
                }
                if (event->user.code & OBJMOD_GLYPHW) {
                    if (obj->info1 == currentTile) {
                        // Clear tool, tile changed from outside source
                        cancelTool();
                        refreshTile();
                        tools->tileRedraw();
                        setDirty(1);
                    }
                }
                if (event->user.code & OBJMOD_TILE) {
                    if ((!isColl) && (currentTile >= obj->info1) && (currentTile <= obj->info2)) {
                        // Clear tool, tile changed from outside source
                        cancelTool();
                        refreshTile();
                        tools->tileRedraw();
                        setDirty(1);
                    }
                }
                if (event->user.code & OBJMOD_COLL) {
                    if ((isColl) && (currentTile >= obj->info1) && (currentTile <= obj->info2)) {
                        // Clear tool, coll map changed from outside source
                        cancelTool();
                        refreshTile();
                        tools->tileRedraw();
                        setDirty(1);
                    }
                }
            }
            if ((event->user.code & OBJ_WORLD) && (obj->obj == world)) {
                if (event->user.code & OBJMOD_DELETE) {
                    tileset = NULL;
                    closeWindow();
                }
                if (event->user.code & OBJMOD_NAME) {
                    updateTitlebar();
                }
            }
            return 1;
    
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONDBL:
            if ((event->button.button == SDL_BUTTON_LEFT) || (event->button.button == SDL_BUTTON_RIGHT)) {
                hideCursor = 1;
                // Signed as it could go off the edge
                clickX = (Sint16)event->button.x / pixelSize;
                clickY = (Sint16)event->button.y / pixelSize;
                // Glyph width?
                if ((clickY >= tileHeight) && (isFont)) {
                    startToolGlyphWidthDrag();
                }
                // If unmodified selection-tool left-click and within selection, this is a selection drag
                else if ((!toolActive) &&
                    ((tool == TOOLS_SELECT) || (tool == TOOLS_SELECTELLIPSE) || (tool == TOOLS_WAND)) &&
                    !(SDL_GetModState() & (KMOD_CTRL | KMOD_ALT)) &&
                    (event->button.button == SDL_BUTTON_LEFT) &&
                    (isInSelection(clickX, clickY))) {
                    
                    startToolOnMove = 0;
                    moveCursor(clickX, clickY);
                    startToolSelectionDrag();
                }
                else {
                    moveCursor(clickX, clickY);
                    startTool(event->button.button == SDL_BUTTON_LEFT ? ColorSelect::SELECTED_FG : ColorSelect::SELECTED_BK);
                }
                return 1;
            }
            else if (event->button.button == SDL_BUTTON_WHEELUP) {
                zoomIn();
                return 1;
            }
            else if (event->button.button == SDL_BUTTON_WHEELDOWN) {
                zoomOut();
                return 1;
            }
            break;
    
        case SDL_MOUSEBUTTONUP:
            finishTool();
            
            // Force cursor back into area, no more virtual
            moveCursor(cursorX, cursorY);
            return 1;
    
        case SDL_MOUSEMOTION:
            if ((event->motion.state & SDL_BUTTON_LMASK) || (event->motion.state & SDL_BUTTON_RMASK)) {
                hideCursor = 1;
                // Signed as it could go off the edge
                moveCursor(((Sint16)event->motion.x) / pixelSize, ((Sint16)event->motion.y) / pixelSize);
            }
            else {
                // Update mouse pointer
                mousePointer(((Sint16)event->motion.x) / pixelSize, ((Sint16)event->motion.y) / pixelSize);
            }
            return 1;

        case SDL_MOUSEFOCUS:
            if (event->user.code & 1) {
                hover = 1;
                mousePointer();
            }
            else {
                hover = 0;
                selectMouse(MOUSE_NORMAL);
            }
            return 1;
        
        case SDL_INPUTFOCUS:
            if (event->user.code & 1) {
                if (!haveFocus) {
                    hideCursor = 0;
                    haveFocus = partialFocus = 1; 
                    changed = 1;
                }
            }
            else if (event->user.code & 2) {
                if (!partialFocus) {
                    partialFocus = 1;
                    changed = 1;
                }
            }
            else {
                if (partialFocus) {
                    partialFocus = 0;
                    changed = 1;
                }
            }
            if (!(event->user.code & 1)) {
                if (haveFocus) {
                    cancelTool();
                    haveFocus = 0;
                    changed = 1;
                }
            }
            
            if (changed) {
                // Refresh selection rectangle and cursor
                Rect rect = { selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h };
                Rect rect2 = { cursorX, cursorY, 1, 1 };
                setDirtyRect(rect);
                setDirtyRect(rect2);
            }
            return 1;            

        case SDL_KEYUP:
            switch (event->key.keysym.sym) {
                case SDLK_LSHIFT:
                case SDLK_RSHIFT:
                case SDLK_SPACE:
                    finishTool();

                    // Force cursor back into area, no more virtual
                    moveCursor(cursorX, cursorY);
                    return 1;
                
                case SDLK_LALT:
                case SDLK_RALT:
                case SDLK_LCTRL:
                case SDLK_RCTRL:
                    modifyTool();
                    return 1;
                
                default:
                    break;
            }
            break;

        case SDL_KEYDOWN:
            hideCursor = 0;

            // We can't stick modifiers in due to the numerous combinations that
            // could occur of ctrl/shift/alt in use of various tools
            switch (event->key.keysym.sym) {
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    // Merge any existing selection
                    try {
                        mergeSelection();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;

                case SDLK_LALT:
                case SDLK_RALT:
                case SDLK_LCTRL:
                case SDLK_RCTRL:
                    modifyTool();
                    return 1;

                case SDLK_SPACE:
                    startTool(ColorSelect::SELECTED_FG);
                    return 1;

                case SDLK_LSHIFT:
                    startToolOnMove = 1;
                    return 1;
                        
                // @TODO: This should probably end up as a configurable shortcut somehow
                case SDLK_RSHIFT:
                    startTool(ColorSelect::SELECTED_BK);
                    return 1;
                        
                case SDLK_RIGHT:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(tileWidth - selectionRect.w - selectionRect.x, selectionYOffs);
                        else moveSelection(selectionXOffs + 1, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(tileWidth - 1, virtualCursorY);
                        else moveCursor(virtualCursorX + 1, virtualCursorY);
                    }
                    return 1;
                        
                case SDLK_END:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(tileWidth - selectionRect.w - selectionRect.x, tileHeight - selectionRect.h - selectionRect.y);
                        else moveSelection(tileWidth - selectionRect.w - selectionRect.x, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(tileWidth - 1, tileHeight - 1);
                        else moveCursor(tileWidth - 1, virtualCursorY);
                    }
                    return 1;
                        
                case SDLK_LEFT:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(-selectionRect.x, selectionYOffs);
                        else moveSelection(selectionXOffs - 1, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(0, virtualCursorY);
                        else moveCursor(virtualCursorX - 1, virtualCursorY);
                    }
                    return 1;

                case SDLK_HOME:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(-selectionRect.x, -selectionRect.y);
                        else moveSelection(-selectionRect.x, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(0, 0);
                        else moveCursor(0, virtualCursorY);
                    }
                    return 1;
                        
                case SDLK_DOWN:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(selectionXOffs, tileHeight - selectionRect.h - selectionRect.y);
                        else moveSelection(selectionXOffs, selectionYOffs + 1);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(virtualCursorX, tileHeight - 1);
                        else moveCursor(virtualCursorX, virtualCursorY + 1);
                    }
                    return 1;

                case SDLK_UP:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(selectionXOffs, -selectionRect.y);
                        else moveSelection(selectionXOffs, selectionYOffs - 1);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(virtualCursorX, 0);
                        else moveCursor(virtualCursorX, virtualCursorY - 1);
                    }
                    return 1;

                case SDLK_PAGEDOWN:
                    if (selectionMode != SELECTION_OUTLINE) {
                        moveSelection(selectionXOffs, selectionYOffs + (viewHeight / pixelSize - 1));
                    }
                    else {
                        moveCursor(virtualCursorX, virtualCursorY + (viewHeight / pixelSize - 1));
                    }
                    return 1;

                case SDLK_PAGEUP:
                    if (selectionMode != SELECTION_OUTLINE) {
                        moveSelection(selectionXOffs, selectionYOffs - (viewHeight / pixelSize - 1));
                    }
                    else {
                        moveCursor(virtualCursorX, virtualCursorY - (viewHeight / pixelSize - 1));
                    }
                    return 1;
                
                default:
                    break;
            }
            break;
    }
    return 0;
}

void TilePaint::zoomOut() { start_func
    if (pixelSize > MAX_ZOOMGRID) changePixelSizing(pixelSize - 1, enableGrid ? 1 : 0);
    else if (pixelSize > 1) changePixelSizing(pixelSize - 1, 0);
    if (pixelSize == 1) tools->findWidget(TilePaintTools::ID_ZOOMOUT)->disable();
    tools->findWidget(TilePaintTools::ID_ZOOMIN)->enable();
}
                    
void TilePaint::zoomIn() { start_func
    if (pixelSize < MAX_ZOOMGRID - 1) changePixelSizing(pixelSize + 1, 0);
    else if (pixelSize < MAX_ZOOM) changePixelSizing(pixelSize + 1, enableGrid ? 1 : 0);
    if (pixelSize == MAX_ZOOM) tools->findWidget(TilePaintTools::ID_ZOOMIN)->disable();
    tools->findWidget(TilePaintTools::ID_ZOOMOUT)->enable();
}

void TilePaint::moveSelection(int newX, int newY) { start_func
    // No dragging of outlines allowed
    if (selectionMode == SELECTION_OUTLINE) return;
    
    if ((newX != selectionXOffs) || (newY != selectionYOffs)) {
        // Dirty old
        Rect rect = { selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h };
        setDirtyRect(rect);
        
        // Move (no limits to where it can move to)
        selectionXOffs = newX;
        selectionYOffs = newY;
        
        // Dirty new
        rect.x = selectionRect.x + newX;
        rect.y = selectionRect.y + newX;
        setDirtyRect(rect);
    }
}

void TilePaint::moveCursor(int newX, int newY) { start_func
    int oldX = cursorX;
    int oldY = cursorY;
    int doTool = 0;
    
    if (startToolOnMove) startTool(ColorSelect::SELECTED_FG);

    // This alone doesn't make anything dirty; normally, this isn't used;
    // anything that does use it, dirties; we don't track virtual cursor
    // if no active tool
    if (toolActive) {
        if ((newX != virtualCursorX) || (virtualCursorY != newY)) {
            virtualCursorX = newX;
            virtualCursorY = newY;
            
            // Tool
            doTool = 1;
        }
    }

    if (newX < 0) newX = 0;
    if (newX >= tileWidth) newX = tileWidth - 1;
    if (newY < 0) newY = 0;
    if (newY >= tileHeight) newY = tileHeight - 1;
    
    if ((newX != cursorX) || (newY != cursorY)) {
        cursorX = newX;
        cursorY = newY;

        // Scroll?
        if (myFrame) myFrame->scrollToView(cursorX * pixelSize,
                                           cursorY * pixelSize,
                                           pixelSize, pixelSize);

        // Dirty
        setDirtyBox(oldX, oldY, cursorX, cursorY);
    }

    if (doTool) dragTool();
    // No active tool, we track virtual cursor, but to CLIPPED coordinates
    else if (!toolActive) {
        virtualCursorX = newX;
        virtualCursorY = newY;
    }
}

void TilePaint::startToolGlyphWidthDrag() { start_func
    assert(tile);
    
    if (toolActive) return;
    
    toolActive = TOOLS_GLYPHWIDTHDRAG;
    mousePointer();
    dragTool(1);
}

void TilePaint::startToolSelectionDrag() { start_func
    assert(tile);

    startToolOnMove = 0;
    if (toolActive) return;

    toolActive = TOOLS_SELECTDRAG;
    toolStartX = virtualCursorX;
    toolStartY = virtualCursorY;
    toolCtrl = SDL_GetModState() & KMOD_CTRL;
    toolAlt = SDL_GetModState() & KMOD_ALT;
    mousePointer();
    dragTool(1);
}

void TilePaint::startTool(int fgbk) { start_func
    assert(tile);

    startToolOnMove = 0;
    if (toolActive) return;

    // Merge any existing selection
    try {
        mergeSelection();
    }
    catch (UndoException& e) {
        return;
    }

    toolMinX = virtualCursorX;
    toolMinY = virtualCursorY;
    toolMaxX = virtualCursorX;
    toolMaxY = virtualCursorY;
    
    toolFgBk = fgbk;
    toolActive = tool;
    toolStartX = virtualCursorX;
    toolStartY = virtualCursorY;
    toolAlpha = alphaBlend;
    toolAlias = antiAlias;
    // Scale tolerance to 500, the max two colors can differ is 1019
    toolTolerance = tolerance * 5;
    toolCtrl = SDL_GetModState() & KMOD_CTRL;
    toolAlt = SDL_GetModState() & KMOD_ALT;
    if (toolFgBk == ColorSelect::SELECTED_FG) {
        toolColor = mapColor32(colors.fg.r, colors.fg.g, colors.fg.b, colors.fg.a * toolAlpha / 100);
    }
    else {
        toolColor = mapColor32(colors.bk.r, colors.bk.g, colors.bk.b, colors.bk.a * toolAlpha / 100);
    }
    
    mousePointer();
    dragTool(1);
}

void TilePaint::modifyTool() { start_func
    if (toolActive) {
        toolCtrl = SDL_GetModState() & KMOD_CTRL;
        toolAlt = SDL_GetModState() & KMOD_ALT;

        // Selection tools need entire area dirtied because this may cause
        // a deleted selection to reappear
        if ((toolActive == TOOLS_SELECT) || (toolActive == TOOLS_SELECTELLIPSE) || (toolActive == TOOLS_WAND)) {
            setDirty(1);
        }
        
        // Selection dragging changes selection type
        if ((toolActive == TOOLS_SELECTDRAG) && (selectionMode != SELECTION_OUTLINE)) {
            if ((toolCtrl) || (toolAlt)) selectionMode = SELECTION_OPAQUE;
            else selectionMode = SELECTION_ALPHA;
        }

        dragTool();
    }

    // Always update mouse pointer even if no active tool
    mousePointer();
}

void TilePaint::dragTool(int firstTime, int lastTime) { start_func
    assert(tile);
    
    // Selection tools merely clear, on right-click
    if ((firstTime) && (toolFgBk == ColorSelect::SELECTED_BK) &&
        ((toolActive == TOOLS_SELECT) ||
         (toolActive == TOOLS_SELECTELLIPSE) ||
         (toolActive == TOOLS_WAND))) {

        try {
            doneSelection();
        }
        catch (UndoException& e) {
        }
        toolActive = 0;
        return;
    }
    
    if (toolActive) {
        int rX, rY;
        int slope1, slope2;
        SDL_Surface* drawTo;
        Rect rect;
        Rect tileBound = { 0, 0, tileWidth, tileHeight };
        Uint8* data;
        int nonDraw = 0;
        if ((toolActive == TOOLS_SELECT) || (toolActive == TOOLS_SELECTELLIPSE) ||
            (toolActive == TOOLS_DROPPER) || (toolActive == TOOLS_WAND) ||
            (toolActive == TOOLS_SELECTDRAG) || (toolActive == TOOLS_GLYPHWIDTHDRAG)) {
            nonDraw = 1;
        }

        if ((tool != TOOLS_PEN) && (!nonDraw)) refreshTile();

        if (((toolAlpha < 100) || ((selectionMode == SELECTION_OUTLINE) && (selectionRect.w))) && (!nonDraw)) {
            // If we're drawing using alpha, start with transparency
            if (toolAlpha < 100) drawRect(0, 0, alphaWorkspace->w, alphaWorkspace->h, mapColor32(0, 0, 0, 0), alphaWorkspace);
            // ...otherwise start with current data
            else blit(0, 0, tile, 0, 0, alphaWorkspace, alphaWorkspace->w, alphaWorkspace->h);
            drawTo = alphaWorkspace;
        }
        else {
            drawTo = tile;
        }
        
        // So that undo warning boxes don't cancel us!
        int tool = toolActive;
        if (lastTime) toolActive = 0;
    
        try {
            switch (tool) {
                case TOOLS_GLYPHWIDTHDRAG:
                    if (lastTime) {
                        refreshTile();
                        undoStoreGlyph();
                        setGlyphWidth(virtualCursorX);
                        applyGlyphWidth();
                    }
                    else {
                        setGlyphWidth(virtualCursorX);
                    }
                    break;
                
                case TOOLS_SELECTDRAG:
                    // Dirty current selection area
                    setDirtyBox(selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs,
                                selectionRect.x + selectionRect.w - 1 + selectionXOffs,
                                selectionRect.y + selectionRect.h - 1 + selectionYOffs);
                
                    // Float selection?
                    if (firstTime) {
                        floatSelection();
                    }
                    else {
                        // Move selection
                        selectionXOffs += virtualCursorX - toolLastX;
                        selectionYOffs += virtualCursorY - toolLastY;
                        
                        // Dirty new area also
                        setDirtyBox(selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs,
                                    selectionRect.x + selectionRect.w - 1 + selectionXOffs,
                                    selectionRect.y + selectionRect.h - 1 + selectionYOffs);
                    }
                    break;
                    
                case TOOLS_WAND:
                case TOOLS_SELECT:
                case TOOLS_SELECTELLIPSE:
                    // Keep a backup copy of old selection
                    if (firstTime) {
                        blit(0, 0, selection, 0, 0, backupSelection, tileWidth, tileHeight);
                        backupSelectionRect = selectionRect;
                    }
                    // Refresh from backup if dragging with ctrl/alt
                    else if ((toolCtrl) || (toolAlt)) {
                        blit(0, 0, backupSelection, 0, 0, selection, tileWidth, tileHeight);
                        selectionRect = backupSelectionRect;
                    }
                    
                    if (tool == TOOLS_WAND) {
                        // We get rectangle from previous time
                        if (!firstTime) {
                            rect.x = toolMinX;
                            rect.y = toolMinY;
                            rect.w = toolMaxX - toolMinX + 1;
                            rect.h = toolMaxY - toolMinY + 1;
    
                            // Mark previous rect dirty
                            setDirtyRect(rect, 1);
                        }
                        else {
                            rect.w = 0;
                        }
                    }
                    else if (tool == TOOLS_SELECT) {
                        rect = createRect(toolStartX, toolStartY, virtualCursorX, virtualCursorY);
                    }
                    else {
                        rX = abs(toolStartX - virtualCursorX);
                        rY = abs(toolStartY - virtualCursorY);
                    
                        // Special case
                        if (rX < 1) rX = 1;
                        if (rY < 1) rY = 1;
    
                        rect = createRect(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                    }
    
                    // We can't have a selection rect that's bigger than the selection surface
                    intersectRects(rect, tileBound);
    
                    // Undo?
                    if (lastTime) {
                        // If we're clearing selection, we must do extra work here
                        if ((!toolCtrl) && (!toolAlt)) {
                            // Refresh from backup
                            blit(0, 0, backupSelection, 0, 0, selection, tileWidth, tileHeight);
                            selectionRect = backupSelectionRect;
                            // Add into rectangle the area we're selecting
                            boundRects(selectionRect, rect);
                            // Clear it AND store undo
                            clearSelection();
                        }
                        else {
                            // Just undo the area we're adding/deleting from
                            if (rect.w) undoStoreSelect(rect.x, rect.y, rect.w, rect.h);
                        }
                    }
                    // Clear selection if no ctrl/alt
                    else if ((!toolCtrl) && (!toolAlt)) clearSelection(0);                
                    
                    // Draw
                    if (tool == TOOLS_WAND) {
                        // Non-contiguous always works
                        if (!toolAlias) {
                            rect = floodFillNonContiguous32(tile, selection, virtualCursorX, virtualCursorY, mapColor32(0, 0, 0, toolAlt ? 0 : 255), toolTolerance);
                        }
                        else {
                            // Use alpha workspace as temp area, use 255/255/255 as filler color
                            drawRect(0, 0, alphaWorkspace->w, alphaWorkspace->h, mapColor32(255, 255, 255, 255), alphaWorkspace);
                            rect = floodFill32(tile, alphaWorkspace, virtualCursorX, virtualCursorY, mapColor32(0, 0, 0, toolAlt ? 0 : 255), toolTolerance);
                            SDL_SetAlpha(alphaWorkspace, 0, 255);
                            SDL_SetColorKey(alphaWorkspace, SDL_SRCCOLORKEY, mapColor32(255, 255, 255, 255));
                            blit(0, 0, alphaWorkspace, 0, 0, selection, tileWidth, tileHeight);
                            SDL_SetColorKey(alphaWorkspace, 0, 0);
                            SDL_SetAlpha(alphaWorkspace, SDL_SRCALPHA, 255);
                        }
    
                        // Remember min/max for undo next time
                        if (rect.w) {
                            toolMinX = rect.x;
                            toolMinY = rect.y;
                            toolMaxX = rect.x + rect.w - 1;
                            toolMaxY = rect.y + rect.h - 1;
                        }
                        else {
                            // Causes no undo area or undirty to occur next frame
                            toolMinX = toolMaxX = -1;
                        }
                    }
                    else if (tool == TOOLS_SELECT) {
                        sge_FilledRect(selection, toolStartX, toolStartY, virtualCursorX, virtualCursorY, mapColor32(0, 0, 0, toolAlt ? 0 : 255));
                    }
                    else {
                        sge_FilledEllipse(selection, toolStartX, toolStartY, rX, rY, mapColor32(0, 0, 0, toolAlt ? 0 : 255));
                    }
                    setDirtyRect(rect, 1);
    
                    // Add to overall selection bounding box
                    if (!toolAlt) boundRects(selectionRect, rect);
                    // ...or fix selection rectangle if removing stuff
                    else fixSelectionRect();
    
                    // Previous
                    if (!firstTime) {
                        if (tool == TOOLS_WAND) {
                            // (was dirtied above, at beginning)
                        }
                        else if (tool == TOOLS_SELECT) {
                            setDirtyBox(toolStartX, toolStartY, toolLastX, toolLastY, 1);
                        }
                        else {
                            rX = abs(toolStartX - toolLastX);
                            rY = abs(toolStartY - toolLastY);
                            if (rX < 1) rX = 1;
                            if (rY < 1) rY = 1;
                            setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY, 1);
                        }
                    }
                    
                    // Allow copy now?
                    if (lastTime) {
                        if (selectionRect.w) tools->findWidget(TilePaintTools::ID_COPY)->enable();
                        else tools->findWidget(TilePaintTools::ID_COPY)->disable();
                    }
                    break;
    
                case TOOLS_DROPPER:
                    data = ((Uint8*)tile->pixels) + cursorX * 4 + cursorY * tile->pitch;
                    colorbar->addColor(toolFgBk, data[0], data[1], data[2], data[3], !firstTime);
                    break;
            
                case TOOLS_PEN:
                    // We don't want to draw the previous pixel twice; this is more important
                    // than the slight degradation in line quality, as lines are only used
                    // if the mouse moves "too fast"
                    if (!lastTime) {
                        if (toolStartX > virtualCursorX) --toolStartX;
                        else if (toolStartX < virtualCursorX) ++toolStartX;
                        if (toolStartY > virtualCursorY) --toolStartY;
                        else if (toolStartY < virtualCursorY) ++toolStartY;
                        sge_Line(drawTo, toolStartX, toolStartY, virtualCursorX, virtualCursorY, toolColor);
                        setDirtyBox(toolStartX, toolStartY, virtualCursorX, virtualCursorY);
                    }
                    
                    // Track area covered for undo
                    if (virtualCursorX < toolMinX) toolMinX = virtualCursorX;
                    if (virtualCursorX > toolMaxX) toolMaxX = virtualCursorX;
                    if (virtualCursorY < toolMinY) toolMinY = virtualCursorY;
                    if (virtualCursorY > toolMaxY) toolMaxY = virtualCursorY;
                    if (toolStartX < toolMinX) toolMinX = toolStartX;
                    if (toolStartX > toolMaxX) toolMaxX = toolStartX;
                    if (toolStartY < toolMinY) toolMinY = toolStartY;
                    if (toolStartY > toolMaxY) toolMaxY = toolStartY;
    
                    toolStartX = virtualCursorX;
                    toolStartY = virtualCursorY;
    
                    if (lastTime) undoStoreBlitBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                    else if (tools) tools->tileRedraw();
                    break;
                    
                case TOOLS_FILL:
                    // Undo based on rectangle from last time
                    if (lastTime) undoStoreBlitBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                    
                    // Dirty previous rectangle
                    if (!firstTime) setDirtyBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                    
                    // Non-contiguous always works
                    if (!toolAlias) {
                        rect = floodFillNonContiguous32(tile, drawTo, virtualCursorX, virtualCursorY, toolColor, toolTolerance);
                    }
                    // Possible situations-
                    // Drawing to tile- works OK if 0 tolerance (fills directly on surface)
                    // Drawing to alpha 100%- works OK if 0 tolerance (fills directly on copy of surface)
                    // Drawing to alpha <100%- works OK always (0,0,0,0 doesn't fill, but would have no effect anyways)
                    // Situations that don't work OK- we must draw to a temp surface and colorkey-blit over
                    else if ((toolTolerance > 0) && ((drawTo == tile) || ((drawTo == alphaWorkspace) && (toolAlpha == 100)))) {
                        // Use bitwise complement for background, so there can't be a conflict
                        drawRect(0, 0, backupSelection->w, backupSelection->h, ~toolColor, backupSelection);
                        rect = floodFill32(tile, backupSelection, virtualCursorX, virtualCursorY, toolColor, toolTolerance);
                        SDL_SetColorKey(backupSelection, SDL_SRCCOLORKEY, ~toolColor);
                        blit(0, 0, backupSelection, 0, 0, drawTo, tileWidth, tileHeight);
                        SDL_SetColorKey(backupSelection, 0, 0);
                    }
                    else {
                        rect = floodFill32(tile, drawTo, virtualCursorX, virtualCursorY, toolColor, toolTolerance);
                    }
                    setDirtyRect(rect);
                    
                    // Remember min/max for undo on last time
                    if (rect.w) {
                        toolMinX = rect.x;
                        toolMinY = rect.y;
                        toolMaxX = rect.x + rect.w - 1;
                        toolMaxY = rect.y + rect.h - 1;
                    }
                    else {
                        // Causes no undo area or undirty to occur next frame
                        toolMinX = toolMaxX = -1;
                    }
                    break;
                    
                case TOOLS_LINE:
                case TOOLS_RECT:
                case TOOLS_RECTFILL:
                    // @TODO: we changed this line, haven't tested it yet
                    if (lastTime) undoStoreBlitBox(toolStartX, toolStartY, virtualCursorX, virtualCursorY);
                    
                    rX = virtualCursorX;
                    rY = virtualCursorY;
                    
                    // Limit to square or straight line?
                    if (toolCtrl) {
                        if (tool == TOOLS_LINE) {
                            // Determine approximate slope of line
                            slope1 = abs(toolStartX - rX);
                            slope2 = abs(toolStartY - rY);
                            // (we only care, if both sizes are > 0)
                            if ((slope1) && (slope2)) {
                                if (slope1 > slope2) swap(slope1, slope2);
                                // slope1/slope2 will be a fraction between 0 (flat) and
                                // 1 (diagonal of 45deg multiple); cutoff point is 0.5
                                if (slope1 * 2 / slope2 >= 1) {
                                    // Square
                                    if (abs(toolStartX - rX) < abs(toolStartY - rY)) {
                                        rY = toolStartY + abs(toolStartX - rX) * (toolStartY < rY ? 1 : -1);
                                    }
                                    else {
                                        rX = toolStartX + abs(toolStartY - rY) * (toolStartX < rX ? 1 : -1);
                                    }
                                }
                                else {
                                    // Flat line
                                    if (abs(toolStartX - rX) < abs(toolStartY - rY)) {
                                        rX = toolStartX;
                                    }
                                    else {
                                        rY = toolStartY;
                                    }
                                }
                            }
                        }
                        else {
                            // Square
                            if (abs(toolStartX - rX) < abs(toolStartY - rY)) {
                                rY = toolStartY + abs(toolStartX - rX) * (toolStartY < rY ? 1 : -1);
                            }
                            else {
                                rX = toolStartX + abs(toolStartY - rY) * (toolStartX < rX ? 1 : -1);
                            }
                        }
                    }
                    
                    if (tool == TOOLS_LINE) {
                        if (toolAlias) sge_AALine(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                        else sge_Line(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                    }
                    else if (tool == TOOLS_RECT) sge_Rect(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                    else sge_FilledRect(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                    setDirtyBox(toolStartX, toolStartY, rX, rY);
                    // (no need to limit based on toolCtrl, as a limited area will always
                    //  be smaller than this area we use here)
                    if (!firstTime) setDirtyBox(toolStartX, toolStartY, toolLastX, toolLastY);
                    break;
                    
                case TOOLS_ELLIPSE:
                case TOOLS_ELLIPSEFILL:
                    rX = abs(toolStartX - virtualCursorX);
                    rY = abs(toolStartY - virtualCursorY);
                
                    // Special case
                    if (rX < 1) rX = 1;
                    if (rY < 1) rY = 1;
                    
                    // Circle?
                    if (toolCtrl) {
                        rX = min(rX, rY);
                        rY = rX;
                    }
    
                    if (lastTime) undoStoreBlitBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
    
                    if (tool == TOOLS_ELLIPSE) {
                        if (toolAlias) sge_AAEllipse(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                        else sge_Ellipse(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                    }
                    else {
                        if (toolAlias) sge_AAFilledEllipse(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                        else sge_FilledEllipse(drawTo, toolStartX, toolStartY, rX, rY, toolColor);
                    }
                    
                    setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
    
                    // Previous
                    if (!firstTime) {
                        rX = abs(toolStartX - toolLastX);
                        rY = abs(toolStartY - toolLastY);
                        if (rX < 1) rX = 1;
                        if (rY < 1) rY = 1;
                        // (no need to limit to a circle based on toolCtrl, this will always
                        // cover the minimum area needed)
                        setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                    }
                    break;
            }
        }
        catch (UndoException& e) {
            toolActive = tool;
            cancelTool();
            return;
        }
        
        if (drawTo == alphaWorkspace) {
            // Clip to selection, alpha blit, or both?
            if ((selectionMode == SELECTION_OUTLINE) && (selectionRect.w)) {
                if (toolAlpha < 100) maskAlphaBlit32(0, 0, alphaWorkspace, 0, 0, tile, 0, 0, selection, 100, tileWidth, tileHeight);
                else maskBlit32(0, 0, alphaWorkspace, 0, 0, tile, 0, 0, selection, tileWidth, tileHeight);
            }
            else alphaBlit32(0, 0, alphaWorkspace, 0, 0, tile, tileWidth, tileHeight);
        }
        
        if ((lastTime) && (!nonDraw)) {
            applyTile();
        }
        else if (!nonDraw) tools->tileRedraw();
        
        toolLastX = virtualCursorX;
        toolLastY = virtualCursorY;        
    }
}

void TilePaint::cancelTool() { start_func
    assert(tile);
    
    startToolOnMove = 0;
    if (toolActive) {
        int rX;
        int rY;
    
        switch (toolActive) {
            case TOOLS_GLYPHWIDTHDRAG:
                rX = glyphWidth;
                refreshTile();
                setDirtyGlyphWidth(rX, glyphWidth);
                // The cursor may appear, and the above won't necessarily cover it
                setDirtyBox(cursorX, cursorY, cursorX, cursorY);
                break;

            case TOOLS_SELECT:
            case TOOLS_SELECTELLIPSE:
            case TOOLS_WAND:
                // Refresh from backup, dirty all
                blit(0, 0, backupSelection, 0, 0, selection, tileWidth, tileHeight);
                selectionRect = backupSelectionRect;
                setDirty(1);
                break;

            case TOOLS_PEN:
            case TOOLS_FILL:
                refreshTile();
                tools->tileRedraw();
                setDirtyBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                break;
                
            case TOOLS_LINE:
            case TOOLS_RECT:
            case TOOLS_RECTFILL:
                refreshTile();
                tools->tileRedraw();
                setDirtyBox(toolStartX, toolStartY, toolLastX, toolLastY);
                break;
                
            case TOOLS_ELLIPSE:
            case TOOLS_ELLIPSEFILL:
                refreshTile();                
                tools->tileRedraw();
                rX = abs(toolStartX - toolLastX);
                rY = abs(toolStartY - toolLastY);
                if (rX < 1) rX = 1;
                if (rY < 1) rY = 1;
                setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                break;
        }
    
        mousePointer();
        toolActive = 0;
    }
}

void TilePaint::finishTool() { start_func
    assert(tile);

    startToolOnMove = 0;
    if (toolActive) {
        dragTool(0, 1);
        mousePointer();
        // toolActive = 0 taken care of by dragtool now
    }
}

void TilePaint::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw range or all
        if (dirty) {
            if (totalDirty) {
                getRect(toDisplay);
                toDisplay.x += xOffset;
                toDisplay.y += yOffset;
            }
            else {
                dirtyRange.x += x + xOffset;
                dirtyRange.y += y + yOffset;
                // Range must include requested update area as well
                boundRects(toDisplay, dirtyRange);
            }
            dirty = totalDirty = 0;
            dirtyRange.w = 0;
            intersectRects(toDisplay, clipArea);
        }

        xOffset += x;
        yOffset += y;
        
        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
            SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_BKFILL]);

            // position of original pixel
            int opX = max(0, (toDisplay.x - xOffset) / pixelSize);
            int pY = max(0, (toDisplay.y - yOffset) / pixelSize);
            
            // drawn position
            int odX = opX * pixelSize + xOffset;
            int dY = pY * pixelSize + yOffset;
            
            // source material
            int pitch = tile->pitch;
            const Uint8* source = (Uint8*)tile->pixels + opX * 4 + pY * pitch;
            int selPitch = selection->pitch;
            const Uint8* selSrc = (Uint8*)selection->pixels + (opX - selectionXOffs) * 4 + (pY - selectionYOffs) * selPitch;
            
            // limits
            int lastY = toDisplay.y + toDisplay.h;
            int lastX = toDisplay.x + toDisplay.w;
            
            // Area we can legally check for selection (outside this area,
            // pointer is not gaurunteed to point to anything)
            int selX1 = -1;
            int selX2 = -1;
            int selY1 = -1;
            int selY2 = -1;
            if (selectionRect.w) {
                selX1 = selectionRect.x + selectionXOffs;
                selX2 = selectionRect.x + selectionRect.w - 1 + selectionXOffs;
                selY1 = selectionRect.y + selectionYOffs;
                selY2 = selectionRect.y + selectionRect.h - 1 + selectionYOffs;
            }
            
            // Temp storage for alpha selections
            Uint8 selTemp[4];
            
            // Draw right/bottom grid line first, because pixels may overwrite
            // this with selection lines
            // Right/bottom grid is always drawn even if grid is "off"
            if (xOffset + tileWidth * pixelSize < lastX) {
                drawVLine(yOffset, yOffset + tileHeight * pixelSize,
                          xOffset + tileWidth * pixelSize, guiPacked[COLOR_GRID], destSurface);
            }
            if (yOffset + tileHeight * pixelSize < lastY) {
                drawHLine(xOffset, xOffset + tileWidth * pixelSize,
                          yOffset + tileHeight * pixelSize, guiPacked[COLOR_GRID], destSurface);
            }
            
            for (; (dY < lastY) && (pY < tileHeight); dY += pixelSize, ++pY) {
                int pX = opX;
                int dX = odX;
                for (; (dX < lastX) && (pX < tileWidth); dX += pixelSize, ++pX, source += 4, selSrc += 4) {
                    const Uint8* data = source;
                
                    int drawT = gridSize;
                    int drawB = 0;
                    int drawL = gridSize;
                    int drawR = 0;
                    
                    // Selection?
                    if ((pX >= selX1) && (pX <= selX2) && (pY >= selY1) && (pY <= selY2)) {
                        if (selSrc[3]) {
                            // Modify data?
                            if (selectionMode == SELECTION_OPAQUE) {
                                data = selSrc;
                            }
                            else if (selectionMode == SELECTION_ALPHA) {
                                int alpha = selSrc[3];
                                int blend = alphaBlend;
                                
                                // Adjust alpha based on alpha option
                                alpha = alpha * blend / 100;
                            
                                // Special cases
                                if ((alpha == 255) || ((source[3] == 0) && (blend == 100))) {
                                    data = selSrc;
                                }
                                else {
                                    data = selTemp;
    
                            		// Scale RGB blend based on alpha of destination
                                    int destAlpha = source[3];
                                    int rgbScale = alpha;
                                    if (destAlpha < alpha) rgbScale = alpha + (256 - alpha) * (alpha - source[3]) / 255;
    					
                				    selTemp[0] = (source[0] + ((selSrc[0] - source[0]) * rgbScale >> 8)) & 0xFF;
                                    selTemp[1] = (source[1] + ((selSrc[1] - source[1]) * rgbScale >> 8)) & 0xFF;
                				    selTemp[2] = (source[2] + ((selSrc[2] - source[2]) * rgbScale >> 8)) & 0xFF;
                                    selTemp[3] = (source[3] + ((255 - source[3]) * alpha >> 8)) & 0xFF;
                                }
                            }
                        
                            // Selection rectangle
                            if (partialFocus) {
                                if ((pX == selX1) || !(selSrc[-1])) drawL = 2;
                                if ((pX == selX2) || !(selSrc[7])) drawR = 2;
                                if ((pY == selY1) || !(selSrc[-selPitch + 3])) drawT = 2;
                                if ((pY == selY2) || !(selSrc[selPitch + 3])) drawB = 2;
                            }
                        }
                    } 

                    // 0 alpha shows different
                    if (data[3] == 0) {
                        drawRect(dX, dY, pixelSize, pixelSize, guiPacked[COLOR_TRANSPARENT1], destSurface);
                        if ((drawDots == VIEW_DOTS_ALPHA) && (pixelSize - gridSize > 2)) {
                            drawRect(dX + 1 + gridSize, dY + 1 + gridSize, pixelSize - 2 - gridSize, pixelSize - 2 - gridSize,
                                     SDL_MapRGB(destSurface->format, 0, 0, 0), destSurface);
                        }
                        else if ((drawDots == VIEW_DOTS_TRANS) && (pixelSize > 1)) {
                            drawPixel(dX + pixelSize/2, dY + pixelSize/2, guiPacked[COLOR_TRANSPARENT2], destSurface);
                        }
                    }
                    else if (data[3] == 255) {
                        drawRect(dX, dY, pixelSize, pixelSize,
                                 SDL_MapRGB(destSurface->format, data[0], data[1], data[2]), destSurface);
                        if ((drawDots == VIEW_DOTS_ALPHA) && (pixelSize - gridSize > 2)) {
                            drawRect(dX + 1 + gridSize, dY + 1 + gridSize, pixelSize - 2 - gridSize, pixelSize - 2 - gridSize,
                                     SDL_MapRGB(destSurface->format, 255, 255, 255), destSurface);
                        }
                    }
                    else {
                        drawRect(dX, dY, pixelSize, pixelSize,
                                 SDL_MapRGB(destSurface->format, data[0] * data[3] >> 8, data[1] * data[3] >> 8, data[2] * data[3] >> 8), destSurface);
                        if ((drawDots == VIEW_DOTS_ALPHA) && (pixelSize - gridSize > 2)) {
                            drawRect(dX + 1 + gridSize, dY + 1 + gridSize, pixelSize - 2 - gridSize, pixelSize - 2 - gridSize,
                                     SDL_MapRGB(destSurface->format, data[3], data[3], data[3]), destSurface);
                        }
                    }
                    
                    // Grid assumes 1-thickness lines
                    if (drawT == 1) drawHLine(dX, dX + pixelSize - 1, dY, guiPacked[COLOR_GRID], destSurface);
                    else if (drawT == 2) drawAntsHLine(dX, dX + pixelSize - 1, dY, desktop->currentSelectionPhase(), destSurface);
                    if (drawL == 1) drawVLine(dY + 1, dY + pixelSize - 1, dX, guiPacked[COLOR_GRID], destSurface);
                    else if (drawL == 2) drawAntsVLine(dY, dY + pixelSize - 1, dX, desktop->currentSelectionPhase(), destSurface);
                    // (B/R can only be == 2 right now)
                    if (drawB) drawAntsHLine(dX, dX + pixelSize - 1, dY + pixelSize - 1, desktop->currentSelectionPhase(), destSurface);
                    if (drawR) drawAntsVLine(dY, dY + pixelSize - 1, dX + pixelSize - 1, desktop->currentSelectionPhase(), destSurface);
                }
                source += pitch - (pX - opX) * 4;
                selSrc += selPitch - (pX - opX) * 4;
            }
            
            // Cursor
            if ((haveFocus) && (!hideCursor)) {
                int cX = xOffset + cursorX * pixelSize;
                int cY = yOffset + cursorY * pixelSize;
    
                if ((cX < toDisplay.x + toDisplay.w) && (cY < toDisplay.y + toDisplay.h) &&
                    (cX + pixelSize + gridSize > toDisplay.x) &&
                    (cY + pixelSize + gridSize > toDisplay.y)) {
                    drawSelectRect(cX, cY, pixelSize + gridSize, pixelSize + gridSize,
                                   guiPacked[COLOR_TILECURSOR], destSurface, desktop->currentCursorAlpha());
                }
            }
            
            // Glyph width
            if (isFont) {
                int atX = xOffset + glyphWidth * pixelSize;
                int atY = yOffset + tileHeight * pixelSize;
                drawVLine(yOffset, atY,
                          atX, guiPacked[COLOR_GLYPHWIDTH], destSurface);
                drawLine(atX, atY + GLYPH_SIZE_POINTER_MARGIN, atX + GLYPH_SIZE_POINTER_HEIGHT,
                         atY + GLYPH_SIZE_POINTER_MARGIN + GLYPH_SIZE_POINTER_HEIGHT,
                         guiPacked[COLOR_GLYPHWIDTH], destSurface);
                drawLine(atX, atY + GLYPH_SIZE_POINTER_MARGIN, atX - GLYPH_SIZE_POINTER_HEIGHT,
                         atY + GLYPH_SIZE_POINTER_MARGIN + GLYPH_SIZE_POINTER_HEIGHT,
                         guiPacked[COLOR_GLYPHWIDTH], destSurface);
                drawHLine(atX - GLYPH_SIZE_POINTER_HEIGHT, atX + GLYPH_SIZE_POINTER_HEIGHT,
                          atY + GLYPH_SIZE_POINTER_MARGIN + GLYPH_SIZE_POINTER_HEIGHT,
                          guiPacked[COLOR_GLYPHWIDTH], destSurface);
            }
        }
    }
}

void TilePaint::undoStoreBlitBox(int x1, int y1, int x2, int y2) throw_Undo { start_func
    if (x1 > x2) swap(x1, x2);
    if (y1 > y2) swap(y1, y2);
    undoStoreBlit(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
}

void TilePaint::undoStoreBlit(int x, int y, int w, int h) throw_Undo { start_func
    if (isColl) world->undo.storeUndoColl(tileset->getId(), currentTile, x, y, w, h, myFrame);
    else world->undo.storeUndoTile(tileset->getId(), currentTile, x, y, w, h, myFrame);
}

void TilePaint::undoStoreGlyph() throw_Undo { start_func
    world->undo.storeUndoTileGlyph(tileset->getId(), currentTile, glyphWidth, myFrame);
}

void TilePaint::undoStoreSelect(int x, int y, int w, int h) throw_Undo { start_func
    world->undo.storeUndoPaintSelection(&selection, x, y, w, h, selectionXOffs, selectionYOffs, selectionMode, myFrame);
}

void TilePaint::clearSelection(int storeUndo) throw_Undo { start_func
    assert(selection);

    if (selectionRect.w) {
        if (storeUndo) undoStoreSelect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        SDL_FillRect(selection, &selectionRect, mapColor32(0, 0, 0, 0));

        // Add in offset so proper area is dirtied
        selectionRect.x += selectionXOffs;
        selectionRect.y += selectionYOffs;
        // (don't need to set feather because we're clearing -entire- selection)
        setDirtyRect(selectionRect);
        
        selectionRect.w = 0;
        selectionXOffs = selectionYOffs = 0;
        selectionMode = SELECTION_OUTLINE;
        tools->findWidget(TilePaintTools::ID_COPY)->disable();
        
        // Was selection oversized? if so, correct this
        if ((selection->w > tileWidth) || (selection->h > tileHeight)) {
            // Ideally, if we free then alloc, this shouldn't fail
            SDL_FreeSurface(selection);
            selection = NULL;
            selection = createSurface32(tileWidth, tileHeight);
            sge_FilledRect(selection, 0, 0, tileWidth, tileHeight, mapColor32(0, 0, 0, 0));
        }
    }
}

void TilePaint::floatSelection() throw_Undo { start_func
    if ((selectionMode == SELECTION_OUTLINE) && (selectionRect.w)) {
        Uint32 bkColor = mapColor32(colors.bk.r, colors.bk.g, colors.bk.b, colors.bk.a);

        world->undo.preUndoBlock();
        undoStoreBlit(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        undoStoreSelect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        world->undo.postUndoBlock();

        maskBlit32(selectionRect.x, selectionRect.y, tile, selectionRect.x, selectionRect.y, selection, selectionRect.x, selectionRect.y, selection, selectionRect.w, selectionRect.h, 1, bkColor);
        selectionMode = SELECTION_ALPHA;
        setDirtyRect(selectionRect);
        // (we fix the rect because we may have floated transparents, so the
        //  actual selection may have reduced in size)
        fixSelectionRect();
        applyTile();
        // (even possible that there's no selection now)
        if (!selectionRect.w) tools->findWidget(TilePaintTools::ID_COPY)->disable();
    }
}

void TilePaint::doneSelection() throw_Undo { start_func
    if (selectionMode == SELECTION_OUTLINE) clearSelection();
    else mergeSelection();
}

void TilePaint::pasteSelection() throw_Undo { start_func
    if (canConvertClipboard(CLIPBOARD_IMAGE)) {
        world->undo.preUndoBlock();

        doneSelection();
        
        int w;
        int h;
        
        clipboardPasteImageSize(&w, &h);
        
        // Do we need a larger selection surface?
        if ((w > tileWidth) || (h > tileHeight)) {
            // (if this fails, nothing's been affected yet)
            SDL_Surface* newSel = createSurface32(max(tileWidth, w), max(tileHeight, h));
            if (selection) SDL_FreeSurface(selection);
            selection = newSel;
            sge_FilledRect(selection, 0, 0, selection->w, selection->h, mapColor32(0, 0, 0, 0));
            // (no need to undo this on exception, larger selection buffer doesn't harm us)
        }

        undoStoreSelect(0, 0, w, h);
        clipboardPasteImage(selection, 0, 0);

        world->undo.postUndoBlock();

        selectionRect.x = 0;
        selectionRect.y = 0;
        selectionRect.w = w;
        selectionRect.h = h;
        selectionMode = SELECTION_ALPHA;
        
        // Possible nothing was actually pasted
        fixSelectionRect();
        if (selectionRect.w) {
            tools->findWidget(TilePaintTools::ID_COPY)->enable();
        
            // @TODO: Move selection to ensure it's visible
        }

        // Dirty
        setDirtyRect(selectionRect);
    }
}

void TilePaint::copySelection() { start_func
    if (selectionRect.w) {
        if (selectionMode == SELECTION_OUTLINE) {
            // We copy to backup selection so we don't affect our outline
            sge_FilledRect(backupSelection, 0, 0, tileWidth, tileHeight, mapColor32(0, 0, 0, 0));
            maskBlit32(selectionRect.x, selectionRect.y, tile, selectionRect.x, selectionRect.y, backupSelection, selectionRect.x, selectionRect.y, selection, selectionRect.w, selectionRect.h);
            clipboardCopy(backupSelection, selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        }
        else {
            // We copy what's in the floating selection
            clipboardCopy(selection, selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        }
    }
}

void TilePaint::deleteSelection() throw_Undo { start_func
    if ((selectionMode == SELECTION_OUTLINE) && (selectionRect.w)) {
        // Delete where outline is
        Uint32 bkColor = mapColor32(colors.bk.r, colors.bk.g, colors.bk.b, colors.bk.a);
        undoStoreBlit(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        maskBlit32(selectionRect.x, selectionRect.y, tile, selectionRect.x, selectionRect.y, tile, selectionRect.x, selectionRect.y, selection, selectionRect.w, selectionRect.h, 1, bkColor);
        setDirtyRect(selectionRect);
        applyTile();
    }
    else if (selectionRect.w) {
        // Delete floating selection
        clearSelection();
    }
}

void TilePaint::mergeSelection() throw_Undo { start_func
    // (no tileset = closing)
    if (((selectionMode == SELECTION_OPAQUE) || (selectionMode == SELECTION_ALPHA)) && (tileset)) {
        world->undo.preUndoBlock();

        // Paste selection, with offset
        if (selectionRect.w) {
            undoStoreBlit(selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h);
            if (selectionMode == SELECTION_OPAQUE) {
                maskBlit32(selectionRect.x, selectionRect.y, selection, selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, tile, selectionRect.x, selectionRect.y, selection, selectionRect.w, selectionRect.h);
            }
            else {
                maskAlphaBlit32(selectionRect.x, selectionRect.y, selection, selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, tile, selectionRect.x, selectionRect.y, selection, alphaBlend, selectionRect.w, selectionRect.h);
            }
        }
        
        // Clear selection
        clearSelection();
        world->undo.postUndoBlock();

        applyTile();
    }
}

int TilePaint::isInSelection(int x, int y) { start_func
    if (!selectionRect.w) return 0;

    x -= selectionXOffs;
    y -= selectionYOffs;

    Uint8* source = (Uint8*)selection->pixels + y * selection->pitch + x * 4;
    return source[3];
}

void TilePaint::fixSelectionRect() { start_func
    assert(selection);

    int minX = -1;
    int maxX;
    int minY;
    int maxY;
    
    Uint8* source = (Uint8*)selection->pixels;
    int pitchOffs = selection->pitch - selection->w * 4;
    // We use size of selection surface insetad of tile size, on purpose
    for (int y = 0; y < selection->h; ++y) {
        for (int x = 0; x < selection->w; ++x) {
            if (source[3]) {
                if (minX == -1) {
                    minX = maxX = x;
                    minY = maxY = y;
                }
                else {
                    if (x < minX) minX = x;
                    else if (x > maxX) maxX = x;
                    if (y < minY) minY = y;
                    else if (y > maxY) maxY = y;
                }
            }
            source += 4;
        }
        source += pitchOffs;
    }
    
    if (minX == -1) {
        selectionRect.w = 0;
    }
    else {
        selectionRect = createRect(minX, minY, maxX, maxY);
    }
}

void TilePaint::mousePointer(int mouseX, int mouseY) { start_func
    lastMouseX = mouseX;
    lastMouseY = mouseY;
    mousePointer();
}

void TilePaint::mousePointer() { start_func
    if (!hover) return;

    if ((toolActive == TOOLS_SELECT) || (toolActive == TOOLS_SELECTELLIPSE) || (toolActive == TOOLS_WAND)) {
        if (toolAlt) {
            selectMouse(MOUSE_SUBTRACT);
            return;
        }
        else if (toolCtrl) {
            selectMouse(MOUSE_ADD);
            return;
        }
    }
    if (toolActive == TOOLS_GLYPHWIDTHDRAG) {
        selectMouse(MOUSE_HORIZ);
        return;
    }
    if (toolActive == TOOLS_SELECTDRAG) {
        selectMouse(MOUSE_FOURDIRECTION);
        return;
    }
    if ((!toolActive) && ((tool == TOOLS_SELECT) || (tool == TOOLS_SELECTELLIPSE) || (tool == TOOLS_WAND))) {
        if (SDL_GetModState() & KMOD_ALT) {
            selectMouse(MOUSE_SUBTRACT);
            return;
        }
        if (SDL_GetModState() & KMOD_CTRL) {
            selectMouse(MOUSE_ADD);
            return;
        }
        
        if (isInSelection(lastMouseX, lastMouseY)) {
            selectMouse(MOUSE_FOURDIRECTION);
            return;
        }
    }    
    if ((!toolActive) && (lastMouseY >= tileHeight) && (isFont)) {
        selectMouse(MOUSE_HORIZ);
        return;
    }
        
    selectMouse(MOUSE_PIXEL);
}

Window::CommandSupport TilePaint::supportsCommand(int code) const { start_func
    switch (code) {
        // Options
        case VIEW_DOTS_NONE:
        case VIEW_DOTS_TRANS:
        case VIEW_DOTS_ALPHA:
            if (isColl) return Window::COMMAND_HIDE;
            return (Window::CommandSupport)(((drawDots == code) ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_RADIO | Window::COMMAND_ENABLE);
            
        case VIEW_GRID:
            return (Window::CommandSupport)((enableGrid ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_ENABLE);

        case TOOLS_ANTIALIAS:
            if (isColl) return Window::COMMAND_HIDE;
            if ((tool == TOOLS_WAND) || (tool == TOOLS_FILL)) return Window::COMMAND_HIDE;
            if ((tool == TOOLS_LINE) || (tool == TOOLS_ELLIPSE) || (tool == TOOLS_ELLIPSEFILL)) {
                return (Window::CommandSupport)((antiAlias ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_ENABLE);
            }
            else return (Window::CommandSupport)((antiAlias ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_DISABLE);

        case TOOLS_CONTIGUOUS:
            if ((tool == TOOLS_WAND) || (tool == TOOLS_FILL)) {
                return (Window::CommandSupport)((antiAlias ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_ENABLE);
            }
            else return Window::COMMAND_HIDE;

        case TOOLS_DROPPER:
            if (isColl) return Window::COMMAND_HIDE;
            return (Window::CommandSupport)(((tool == code) ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_RADIO | Window::COMMAND_ENABLE);
            
        case TOOLS_PEN:
        case TOOLS_LINE:
        case TOOLS_RECT:
        case TOOLS_RECTFILL:
        case TOOLS_ELLIPSE:
        case TOOLS_ELLIPSEFILL:
        case TOOLS_SELECT:
        case TOOLS_SELECTELLIPSE:
        case TOOLS_FILL:
        case TOOLS_WAND:
            return (Window::CommandSupport)(((tool == code) ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_RADIO | Window::COMMAND_ENABLE);

        case VIEW_DOTS_CHOOSE:
        case TOOLS_BLENDUP:
        case TOOLS_BLENDDOWN:
        case TOOLS_EDITCOLOR:
            return isColl ? Window::COMMAND_HIDE : Window::COMMAND_ENABLE;
            
        // These are always available
        case TOOLS_CHOOSE:
        case VIEW_PREV:
        case VIEW_NEXT:
        case EDIT_SELECTALL:
        case TOOLS_SETTINGS:
        case TOOLS_NEXTCOLOR:
        case TOOLS_PREVCOLOR:
            return Window::COMMAND_ENABLE;

        case TOOLS_TOLERANCEUP:
        case TOOLS_TOLERANCEDOWN:
            if (isColl) return Window::COMMAND_HIDE;
            if ((tool == TOOLS_WAND) || (tool == TOOLS_FILL)) return Window::COMMAND_ENABLE;
            else return Window::COMMAND_DISABLE;

        case EDIT_COPY:
        case EDIT_CUT:
        case EDIT_DELETE:
        case EDIT_DESELECTALL:
            if (selectionRect.w) return Window::COMMAND_ENABLE;
            else return Window::COMMAND_DISABLE;
        
        case EDIT_PASTE:
            if (canConvertClipboard(CLIPBOARD_IMAGE)) return Window::COMMAND_ENABLE;
            else return Window::COMMAND_DISABLE;
            
        case EDIT_SETGLYPHWIDTH:
            if (isFont) return Window::COMMAND_ENABLE;
            return Window::COMMAND_HIDE;
            
        case VIEW_ZOOMIN:
            if (pixelSize >= MAX_ZOOM) return Window::COMMAND_DISABLE;
            else return Window::COMMAND_ENABLE;
            
        case VIEW_ZOOMOUT:
            if (pixelSize == 1) return Window::COMMAND_DISABLE;
            else return Window::COMMAND_ENABLE;
            
        // These aren't done yet (remember some may not be appropriate for isColl)
        case EDIT_FLIP:
        case EDIT_MIRROR:
        case EDIT_SIZEROTATE:
        case EDIT_GRADIENT:
        case EDIT_COLORIZE:
        case EDIT_FILTER:
            return Window::COMMAND_DISABLE;
    }
    
    return world->supportsCommand(code);
}

void TilePaint::startToolIfMove() { start_func
    startToolOnMove = 1;
}

void TilePaint::refresh() { start_func
    if (pixelSize >= MAX_ZOOMGRID) changePixelSizing(pixelSize, enableGrid ? 1 : 0);
    else changePixelSizing(pixelSize, 0);
    setDirty(1);
}

void TilePaint::undoNotify(int undoType, int undoItemId, int undoItemSubId, int uX, int uY, int uW, int uH) { start_func
    if ((undoType == UndoBuffer::UNDO_TILE) ||
        (undoType == UndoBuffer::UNDO_TILECOLL) ||
        (undoType == UndoBuffer::UNDO_TILEGLYPH)) {
        assert(tileset->getId() == undoItemId);
    
        // Ensure proper tile is selected
        changeTile(undoItemSubId);
    
        // Scroll?
        if ((myFrame) && (undoType != UndoBuffer::UNDO_TILEGLYPH)) {
            myFrame->scrollToView(uX * pixelSize, uY * pixelSize,
                                  uW * pixelSize, uH * pixelSize);
        }
    }

    if (undoType == UndoBuffer::UNDO_PAINTSEL) {
        // Scroll?
        if (myFrame) myFrame->scrollToView(uX * pixelSize, uY * pixelSize,
                                           uW * pixelSize, uH * pixelSize);
    }
}

void TilePaint::swapSelectionParameters(int& xoffs, int& yoffs, int& mode) { start_func
    swap(xoffs, selectionXOffs);
    swap(yoffs, selectionYOffs);
    swap(mode, selectionMode);

    // Refresh
    fixSelectionRect();
    setDirty(1);
}

Window::WindowType TilePaint::windowType() const { start_func
    return WINDOW_CLIENT;
}

